package com.eralink.uhfreaderlib;

import android.util.Log;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingDeque;

class Client {
    private static final String TAG = "Client";

    private static final int BufferMaxSize = 1024;
    private final byte[] buffer = new byte[1024];
    private final int[] readSize = new int[1];

    private final byte reader_id;
    private long lastInteractionTime;
    private String hostString;
    private String sn = null;
    private boolean markedAsRemoved = false;

    private SocketChannel socketChannel;
    private ByteBuffer sendByteBuffer;
    private ByteBuffer receiveByteBuffer;

    public Client(byte reader_id, SocketChannel client) {
        this.reader_id = reader_id;
        this.socketChannel = client;
        sendByteBuffer = ByteBuffer.allocate(1024);
        receiveByteBuffer = ByteBuffer.allocate(1024);
        lastInteractionTime = System.currentTimeMillis();
    }

    public byte getReaderId() {
        return reader_id;
    }

    public void setHostString(String hostString) {
        this.hostString = hostString;
    }

    public String getHostString() {
        return hostString;
    }

    public void setSn(String sn) {
        this.sn = sn;
    }

    public String getSn() {
        if (sn == null) {
            return "";
        }

        return sn;
    }

    public long getLastInteractionTime() {
        return lastInteractionTime;
    }

    public void setMarkedAsRemoved(boolean markedAsRemoved) {
        this.markedAsRemoved = markedAsRemoved;
    }

    public boolean isMarkedAsRemoved() {
        return markedAsRemoved;
    }

    public void close() {
        try {
            socketChannel.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        socketChannel = null;
        sendByteBuffer = null;
        receiveByteBuffer = null;
    }

    public int read(final LinkedBlockingDeque<CacheNode> reportQueue) {
        if (0 > readSize[0]) {
            readSize[0] = 0;
        }

        try {
            int nBytesRead = socketChannel.read(receiveByteBuffer);
            while (0 < nBytesRead) {
                receiveByteBuffer.flip();
                while (receiveByteBuffer.hasRemaining()) {
                    int remaining = receiveByteBuffer.remaining();
                    if (remaining <= (BufferMaxSize - readSize[0])) {
                        receiveByteBuffer.get(buffer, readSize[0], remaining);
                        readSize[0] += remaining;
                    } else {
                        receiveByteBuffer.get(buffer, readSize[0], BufferMaxSize - readSize[0]);
                        readSize[0] = BufferMaxSize;
                    }
                    parseReport(reportQueue);
                }

                receiveByteBuffer.clear();
                nBytesRead = socketChannel.read(receiveByteBuffer);
                lastInteractionTime = System.currentTimeMillis();
            }
            if (-1 == nBytesRead) {
                return EraLinkUHFReaderApiError.ErrCommunicationError;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return EraLinkUHFReaderApiError.ErrCommunicationError;
        }

        return EraLinkUHFReaderApiError.ErrNoError;
    }

    private void parseReport(final LinkedBlockingDeque<CacheNode> reportQueue) {
        // Header[2] | Command | Address | Len_High | Len_Low | Data[0…N]  | BCC
        //  2 bytes  | 1 byte  |  1 bytes | 1 byte  |  1 byte  | 0~N bytes  | 1 byte
        // 0x43 0x4D |   CMD   |    0x00  |  N * 256  |  n  |   ---      |
        if (7 > readSize[0]) {
            return;
        }

        int index = 0;
        while (index < readSize[0] - 1) {
            if (0x43 != buffer[index] || 0x4D != buffer[index + 1]) {
                ++index;
                continue;
            }

            if (readSize[0] < (index + 7)) {
                break;
            }

            final int body_len = Utils.u16FromLittleEndianU8s(buffer, index + 4);
            if (513 < body_len) {// data区最大长度512
                ++index;
                continue;
            }
            if (readSize[0] < (index + body_len + 7)) { // 数据不完整，需要继续接收
                break;
            }

            if (buffer[index + 6 + body_len] != Utils.bcc(buffer, index + 6, body_len)) {
                Log.e(TAG, String.format("reader[%s] bad data: %s.", String.valueOf(reader_id), Utils.bytesToHexString(buffer, 0, readSize[0])));
                // BCC校验不合法
                ++index;
                continue;
            }

            CacheNode cacheNode = CachePool.getInstance().obtain(8 + body_len);
            cacheNode.getBuffer()[0] = reader_id;
            System.arraycopy(buffer, index, cacheNode.getBuffer(), 1, 7 + body_len);
            cacheNode.setBufferSize(8 + body_len);
            reportQueue.add(cacheNode);

            index += (body_len + 7);
        }

        if (0 != index) {
            readSize[0] -= index;
            if (readSize[0] < 0) {
                readSize[0] = 0;
            } else {
                System.arraycopy(buffer, index, buffer, 0, readSize[0]);
            }
        }
    }

    public int write(final byte[] data, final int offset, final int length) {
        sendByteBuffer.clear();
        sendByteBuffer.put(data, offset, length);
        sendByteBuffer.flip();

        Callable<Integer> callable = () -> {
            try {
                while (sendByteBuffer.hasRemaining()) {
                    socketChannel.write(sendByteBuffer);
                }
            } catch (IOException e) {
                e.printStackTrace();
                return EraLinkUHFReaderApiError.ErrCommunicationError;
            }
            return EraLinkUHFReaderApiError.ErrNoError;
        };
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread sendThread = new Thread(futureTask);
        sendThread.start();

        int ret;
        try {
            ret = futureTask.get();
        } catch (ExecutionException | InterruptedException e) {
            e.printStackTrace();
            ret = EraLinkUHFReaderApiError.ErrRequestFailed;
        }

        return ret;
    }
}
